//
//  FreebaseResponse.m
//  Freebase
//
//  Created by Alec Flett on 7/26/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "FreebaseQuerier.h"
#import "ImageAttacher.h"
#import "JSON/JSON.h"


// Thanks to the folks at MobileOrchard
// http://www.mobileorchard.com/tutorial-json-over-http-on-the-iphone/

@implementation FreebaseQuerier

@synthesize delegate;
@synthesize queryName;
@synthesize wantsRawData = _wantsRawData;
@synthesize isJson = _isJson;
@synthesize isNamedResponse = _isNamedResponse;
@synthesize isResultResponse = _isResultResponse;
@synthesize name = queryName;

- (FreebaseQuerier *)initWithDelegate:(id)del 
                         respSelector:(SEL)sel 
                            queryName:(NSString*)qName 
                              session:(FreebaseSession*)session
{
    delegate = [del retain];    
    responseSelector = sel;

    _session = [session retain];
    
    if (!qName)
        self.queryName = @"q";
    else
        self.queryName = qName;
        
    _isJson = YES;
    _isNamedResponse = YES;
    _isResultResponse = YES;
    return self;
}

- (void)dealloc {
    [self cancel];
    [_session release];
    [queryName release];
    [super dealloc];
}


+ (NSString*)relativeUrlForService: (NSString*)url withArgs:(NSDictionary*)args
{
    // convert NSDictionary into urlencoded string
    NSMutableArray *argStrings = [NSMutableArray array];
    for (id key in args) {
        id value = [args objectForKey:key];
        NSString *valueString;
        
        if ([value isKindOfClass:[NSString class]])
            valueString = value;
        else {
            NSObject* v = value;
            valueString = [v JSONRepresentation];
        }
        
        valueString = [valueString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];    
        
        NSString *urlstr = [NSString stringWithFormat:@"%@=%@", key, valueString];
        
        [argStrings addObject:urlstr];
    }
    
    NSString *queryString = [argStrings componentsJoinedByString:@"&"];
    
    if (queryString)
        return [NSString stringWithFormat:@"%@?%@", url, queryString];
    
    NSLog(@"Could not create for %@", url);
    return nil;
}


- (void)requestUrl: (NSString*)relativeUrl
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@%@", _session.hostname, relativeUrl]];
	NSURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [_session beginRequest:self];
	_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}


- (void)requestUrl: (NSString*)relativeUrl cachePolicy:(NSURLCacheStoragePolicy)policy
{
    // same as above but with a cache policy
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@%@", _session.hostname, relativeUrl]];
	NSURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:policy timeoutInterval:20.0];
    [_session beginRequest:self];
	_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)requestUrl: (NSString*)relativeUrl 
          postBody:(NSData*)postBody
{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@%@", _session.hostname, relativeUrl]];
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // safe to add to all requests
    [request addValue:@"XMLHttpRequest" forHTTPHeaderField:@"X-Requested-With"];
    
    [request setHTTPBody:postBody];
    [request setHTTPMethod:@"POST"];
    
    [_session beginRequest:self];
	_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)requestUrl:(NSString*)relativeUrl
          postForm:(NSDictionary*)postForm
{

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@%@", _session.hostname, relativeUrl]];
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    // safe to add to all requests
    [request addValue:@"XMLHttpRequest" forHTTPHeaderField:@"X-Requested-With"];
    
    NSMutableArray *r = [[NSMutableArray alloc] init];
    for (NSString* key in postForm) {
        // this is ugly as hell.
        NSString* value = [postForm objectForKey:key];
        NSString* encoded_value =
        (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                            (CFStringRef)value,
                                                            NULL,
                                                            (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                            kCFStringEncodingUTF8);
        [r addObject:[NSString stringWithFormat:@"%@=%@", 
                      key,
                      encoded_value]];
        [encoded_value release];
    }
    NSString *postBody = [r componentsJoinedByString:@"&"];
    
    [request setHTTPBody:[postBody dataUsingEncoding:NSUTF8StringEncoding]];
    [request setHTTPMethod:@"POST"];
    
    [_session beginRequest:self];
	_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
#pragma unused(connection)
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
        _responseCode = [httpResponse statusCode];        
        NSDictionary *headers = [httpResponse allHeaderFields];
        
        for (NSString *headerName in headers)
            if ([[headerName lowercaseString] isEqualToString:@"x-metaweb-tid"])
                _transactionId = [headers objectForKey:headerName];
        
    }
    responseData = [[NSMutableData alloc] initWithCapacity:[response expectedContentLength]+100];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
#pragma unused(connection)
    
	[responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
#pragma unused(connection)
    assert(connection == _connection);
    
    [_session endRequest:self];
    
    // kill the one that failed
    if (!_cancelled) {
        NSLog(@"didFailWithError %@ for query %@", error, queryName);
        [self.delegate errorDidOccur:error request:self];
        
    }
    [responseData release];
    responseData = nil;
    
    [connection release];
    _connection = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // technically these are the same objects, but this is the protocol for releasing everything
    assert(connection == _connection);
    [_session endRequest:self];

    [connection release];
    _connection = nil;
    
    // don't call callbacks
    if (_cancelled) {
        [responseData release];
        responseData = nil;
        return;
    }
    
    NSLog(@"Returned status %d transaction_id: %@", _responseCode, _transactionId);
    if (_responseCode != 200 && _responseCode != 0) {
        NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        if (_isJson) {
            NSDictionary *jsonresult = [responseString JSONValue];
            [self.delegate errorDidOccur:jsonresult request:self];
        } else {
            [self.delegate errorDidOccur:responseString request:self];            
        }
        [responseString release];
        [responseData release];
        responseData = nil;
        [self release];
        return;
    }
        
        
    if (_wantsRawData) {
        [FreebaseQuerier invokeCallback:responseSelector
                                 target:delegate
                                 result:responseData 
                                   name:queryName 
                                 cursor:nil];
        //NSLog(@"Returned transaction_id: %@", [jsonresult objectForKey:@"transaction_id"]);
        
        [responseData release];
        responseData = nil;
        
        // this is the one we 'leak' in the original message
        [self release];
        return;
    }
    
    // ugh, the JSON Library should handle NSData - bug ugly copy here
    NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
	[responseData release];
    responseData = nil;
    
    if (responseString) {
        // and copy 2 into the json structure:(
        if (_isJson) {
            NSDictionary *jsonresult = [responseString JSONValue];
            [responseString release];
                        
            if (jsonresult) {
                NSDictionary *result = jsonresult;
                
                if (_isNamedResponse)
                    result = [jsonresult objectForKey:queryName];
                
                if (result) {
                    NSString *cursor=nil;
                    // extract cursor if any
                    if ([result isKindOfClass:[NSDictionary class]])
                        cursor = [result objectForKey:@"cursor"];
                    
                    if (_isResultResponse) {
                        result = [result objectForKey:@"result"];
                    }
                    
                    if (result) {
                        [FreebaseQuerier invokeCallback:responseSelector 
                                                 target:delegate 
                                                 result:result
                                                   name:queryName
                                                 cursor:cursor];
                        [self release];
                    } else {
                        NSLog(@"Couldn't extract 'result' from query from %@\n%@", result);
                    }
                } else {
                    NSLog(@"Couldn't find %@ in %@", queryName, jsonresult);
                }
            } else {
                NSLog(@"Couldn't convert JSON response");
            }
        } else {
            // not json, just send data as-is (like an image or article load or something)
            [FreebaseQuerier invokeCallback:responseSelector
                                     target: delegate
                                     result: responseString 
                                       name:queryName 
                                     cursor:nil];
            [responseString release];
            [self release];
        }
    } else {
        NSLog(@"Couldn't make responseString from %@ loading %@", responseData, queryName);
    }

}


// thanks GData!
// from http://code.google.com/p/gdata-objectivec-client/source/browse/trunk/Source/BaseClasses/GDataServiceBase.m
+ (void)invokeCallback:(SEL)callbackSel
                target:(id)target
                result:(NSObject*)result
                  name:(NSString*)name
                cursor:(NSString*)cursor
{
    
    // GData fetch callbacks have no return value
    NSMethodSignature *signature = [target methodSignatureForSelector:callbackSel];
    if (!signature)
        NSLog(@"No signature for %@", NSStringFromSelector(callbackSel));
    NSUInteger argcount = [signature numberOfArguments];
    NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature];
    [retryInvocation setSelector:callbackSel];
    [retryInvocation setTarget:target];
    if (argcount>2) {
        [retryInvocation setArgument:&result atIndex:2];
        if (argcount>3) {
            [retryInvocation setArgument:&name atIndex:3];
            if (argcount>4) {
                [retryInvocation setArgument:&cursor atIndex:4];
            }
        }
    }
    [retryInvocation invoke];
}

- (void)cancel {
    
    // safe to cancel multiple times.
    if (_cancelled)
        return;
        
    _cancelled = YES;
    
    // calling cancel means didFinishLoading or errorDidOccur will get called,
    // which will in turn release _connection
    [_connection cancel];
    [_session endRequest:self];

}

@end
